#pragma once

#include "panel.h"
#include "templatevector.h"

class ParentPanel : public Panel
{
protected:
	/** the child UI elements */
	TemplateVector<Panel*> m_children;
	/** which child UI element is selected */
	Panel * m_focus;
	// TODO replace booleans below with bits from an integer
	/** whether or not to keep the focus on the current panel while the mouse moves */
	bool m_changeFocusWhenMoving;
	/** if this panel should scroll */
	bool m_childrenScrollable;
	/** if this parent panel is allowed to have no child it is focusing on. true by default */
	bool m_allowFocusToBeNull;
	/** start in upper-left corner, to right, then next row at the end of the line */
	bool m_autoArrangeIntoGrid;
	/** allows focused element to be dragged by the user. will re-index an element if m_autoArrangeIntoGrid is set */
	bool m_allowChildDragAndDrop;
	/** this panel expands to show all children inside */
	bool m_growToFitAllChildren;
	/** area that all children use. used in scrolling */
	SRect m_childArea;
	/** how fast to scroll when keys are pressed */
	SPoint m_keyScrollSpeed;
	/** where the children are currently scrolled to */
	SPoint m_scroll;

	void singleChildUpdate(Panel * p)
	{
		// request that the ParentPanel refresh itself to make room for the new child
		requestChildAreaRefresh();
		// if the parent is one that MUST have a focus, and it doesnt have one
		if(!m_allowFocusToBeNull && !m_focus)
		{
			// this child should be the new focus for the parent
			m_focus = p;
		}
	}
public:
	/** auto-arrange children */
	void autoArrangeChildren()
	{
		if(m_autoArrangeIntoGrid)
		{
			SRect * area = getArea();
			SPoint cursor(area->getMin());
			SRect * cRect;
			int maxH = 0;
			for(int i = 0; i < m_children.size(); ++i)
			{
				cRect = m_children.get(i)->getArea();
				if(cursor.getX() != area->getMinX()
				&& cursor.getX()+cRect->getWidth() > area->getMaxX())
				{
					cursor.addY(maxH);
					cursor.setX(area->getMinX());
					maxH = 0;
				}
				cRect->setPosition(cursor);
				cursor.addX(cRect->getWidth());
				if(cRect->getHeight() > maxH)
				{
					maxH = cRect->getHeight();
				}
			}
		}
	}
	ParentPanel():m_focus(0),m_changeFocusWhenMoving(true),
		m_childrenScrollable(false),m_allowFocusToBeNull(true),
		m_allowChildDragAndDrop(false),m_autoArrangeIntoGrid(false),
		m_growToFitAllChildren(false){}
	void release(){m_children.release();}
	~ParentPanel(){release();}
	inline void requestChildAreaRefresh(){m_childArea.set(0,0,0,0);}
	/** @return true if the given panel was added as a child of this ParentPanel */
	virtual bool insertChild(const int & a_index, Panel * p)
	{
		// make this ParentPanel the parent of this panel5
		p->setParent(this);
		// and this panel the child of this ParentPanel
		m_children.insert(a_index, p);
		singleChildUpdate(p);
		return true;
	}
	/** @param return the child that is being abandoned by the parent panel */
	virtual Panel * abandonChild(const int & a_index)
	{
		Panel * p = 0;
		if(a_index < m_children.size())
		{
			Panel * p = m_children.get(a_index);
			m_children.remove(a_index);
		}
		return p;
	}
	/**
	 * move the child at a_from to a_to, moving all children correctly as a result
	 */
	void changeChildOrder(const int & a_from, const int & a_to)
	{
		if(a_to != -1 && a_from != a_to)
		{
			Panel * p = m_children.get(a_from);
			// moving the child up
			if(a_from < a_to)
			{
				// move the rest down
				m_children.moveDown(a_from, -1, a_to+1);
			}
			// moving the child down
			else if(a_from > a_to)
			{
				// move the rest up
				m_children.moveUp(a_to, 1, a_from+1);
			}
			m_children.set(a_to, p);
		}
	}
	/** @return true if the given panel was added as a child of this ParentPanel */
	virtual bool addChild(Panel* p)
	{
		int index = m_children.size();
		return insertChild(index, p);
	}
	virtual bool setChild(const int & a_index, Panel * p)
	{
		// make this ParentPanel the parent of this panel
		p->setParent(this);
		// and this panel the child of this ParentPanel
		m_children.set(a_index, p);
		singleChildUpdate(p);
		return true;
	}

	bool contains(Panel * a_childPanel)
	{
		return m_children.indexOf(a_childPanel) >= 0;
	}
	inline void setChildrenScrollable(const bool & a_scrollable){m_childrenScrollable=a_scrollable;}
	inline void setAutoArrangeGrid(const bool & a_autoGrid){m_autoArrangeIntoGrid=a_autoGrid;}
	inline void setDragAndDropChild(const bool & a_dragNdrop){m_allowChildDragAndDrop=a_dragNdrop;}
	inline void setGrowToFitChildren(const bool & a_grow){m_growToFitAllChildren=a_grow;}
	
	void setNullFocusAllowed(const bool & a_allowFocusToBeNull)
	{
		m_allowFocusToBeNull=a_allowFocusToBeNull;
		if(!m_allowFocusToBeNull && !m_focus && m_children.size() > 0)
		{
			m_focus = m_children.get(0);
		}
	}
	inline Panel * getFocus(){return m_focus;}
	TemplateVector<Panel*> * getChildren(){return &m_children;}

	virtual bool update(const int & a_ms)
	{
		bool updated = false;
		for(int i = 0; i < m_children.size(); ++i)
		{
			updated |= m_children.get(i)->update(a_ms);
		}
		return updated;
	}

	void refreshArea()
	{
		if(!m_childArea.isValid() && m_children.size() > 0)
		{
			Panel * p;
			// ask children to size themselves how they would like
			for(int i = 0; i < m_children.size(); ++i)
			{
				p = m_children.get(i);
				p->refreshArea();
			}
			// arrange them in the panel how the parent would like
			autoArrangeChildren();
			// once the children are arranged, calculate how much space they are taking up
			for(int i = 0; i < m_children.size(); ++i)
			{
				if(!m_childArea.isValid()){
					m_childArea.set(*m_children.get(i)->getArea());
				}else{
					m_childArea.add(*m_children.get(i)->getArea());
				}
			}
			// if this parent is obligated to be just big enough for children
			if(m_growToFitAllChildren)
			{
				setArea(m_childArea);
			}
		}
	}
	virtual void setArea(const SRect & a_dimension)
	{
		setDirty(0);
		m_keyScrollSpeed.set(a_dimension.getDimension());
		m_keyScrollSpeed.divide(5);
		Panel::setArea(a_dimension);
		// request recalculation of elements, since children may change their size based on parents...
		requestChildAreaRefresh();
		refreshArea();
		setDirty(0);
	}
	inline void removeAllChildren()
	{
		m_children.clear();
		requestChildAreaRefresh();
		setDirty(0);
	}

	Panel * childAt(SPoint & mouse)
	{
		Panel * childAtMouse = 0;
		for(int i = 0; !childAtMouse && i < m_children.size(); ++i)
		{
			if(m_children.get(i)->getArea()->contains(mouse))
				childAtMouse = m_children.get(i);
		}
		return childAtMouse;
	}

	/**
	 * @param a_dir where to move all the children to
	 * @param a_leadChild which child (by index) to count as the 0,0 point
	 */
	void shiftAllChildren(const SPoint & a_dir, const int & a_leadChild)
	{
		TemplateVector<Panel*> * children = getChildren();
		if(children->size() > a_leadChild)
		{
			SRect * area;
			SPoint change(
				a_dir.getX()+getArea()->getX() - children->get(a_leadChild)->getArea()->getX(),
				a_dir.getY()+getArea()->getY() - children->get(a_leadChild)->getArea()->getY());
			for(int i = 0; i < children->size(); ++i)
			{
				area = children->get(i)->getArea();
				area->setPosition(area->getPosition().sum(change));
			}
		}
		//// if some scrolling bug happens, uncomment this code... that might fix it.
		//requestChildAreaRefresh();
		//refreshArea();
		setDirty(0);
	}

	void ensureViewOf(const SRect & a_rect)
	{
		m_childArea.keepBound(a_rect);
		m_childArea.keepBound(*getArea());
		// set the scroll area to the bounds-corrected new location
		m_scroll.set(m_childArea.getPosition().difference(getArea()->getPosition()));
		// now that we know the scroll is safe, make the scroll happen.
		shiftAllChildren(m_scroll, 0);
	}

	/**
	 * @param a_dir how many key-presses to scroll. example:
	 * keyScroll(SPoint(0,-1)) will scroll down, because y = -1
	 * keyScroll(SPoint(-1,0)) will scroll right, because x = -1
	 */
	inline void keyScroll(const SPoint & a_dir)
	{
//		refreshArea();
		// move the scroll based on the parameter
		m_scroll.add(a_dir.product(m_keyScrollSpeed));
		// potnetially where the location of the children be...
		SPoint scrolledArea(m_scroll);
		SPoint offset(getArea()->getPosition());
		scrolledArea.add(offset);
		m_childArea.setPosition(scrolledArea);
		// bind the potential new location to the scrollable area
		m_childArea.keepBound(*getArea());
		// set the scroll area to the bounds-corrected new location
		m_scroll.set(m_childArea.getPosition().difference(offset));
		// now that we know the scroll is safe, make the scroll happen.
		shiftAllChildren(m_scroll, 0);
	}

	/** @return true if the focus changed */
	bool setFocus(Panel * a_nextFocus)
	{
		bool isNewFocus = a_nextFocus != m_focus;
		if(isNewFocus && (m_allowFocusToBeNull || a_nextFocus))
			m_focus = a_nextFocus;
		return isNewFocus;
	}

	bool handleEvent(const SDL_Event & e)
	{
		// if the event was handled
		bool handled = false;
		// if the event was not only handled, but now the panel must be redrawn to reflect changes.
		bool dirty = false;

		// first, see if the child can use this event... they are the future, after all.
		if(m_focus)
			handled = m_focus->handleEvent(e);

		switch(e.type)
		{
		case SDL_MOUSEBUTTONDOWN:
			// if the children are scrollable, and the focused child didn't do anything
			if(m_childrenScrollable && !handled)
			{
				// scroll the children based on mouse wheel
				switch(e.button.button)
				{
				case SDL_BUTTON_WHEELUP:	keyScroll(SPoint(0, 1));	dirty = true;	break;
				case SDL_BUTTON_WHEELDOWN:	keyScroll(SPoint(0,-1));	dirty = true;	break;
				case SDL_BUTTON_LEFT:
					if(m_changeFocusWhenMoving)
					{
						handled = setFocus(childAt(SPoint(e.button.x,e.button.y)));
						// if nothing is at the mouse click, keep the focus changing
						m_changeFocusWhenMoving = m_focus == 0;
					}
					break;
				}
			}
			break;
		case SDL_MOUSEMOTION:
			// if the focus should change while moving the mouse, tracking the mouse
			if(m_changeFocusWhenMoving)
			{
				handled = setFocus(childAt(SPoint(e.motion.x,e.motion.y)));
			}
			break;
		case SDL_MOUSEBUTTONUP:{
			Panel * child = childAt(SPoint(e.button.x,e.button.y));
			bool handleThisRelease = false;
			switch(e.button.button)
			{
			case SDL_BUTTON_LEFT:	handleThisRelease = true;	break;
			}
			if(handleThisRelease && !handled && m_allowChildDragAndDrop)
			{
				int indexFocus = m_children.indexOf(m_focus);
				int indexDrop = m_children.indexOf(child);
				changeChildOrder(indexFocus, indexDrop);
				setDirty(0);
				requestChildAreaRefresh();
				refreshArea();
			}
			// now that the mouse has been released, the focus is allowed to change
			m_changeFocusWhenMoving = true;
			handled = setFocus(child);
			break;}
		case SDL_KEYDOWN:
			// if the children are scrollable, and the focused child didn't do anything
			if(m_childrenScrollable && !handled)
			{
				switch(e.key.keysym.sym)
				{
				case SDLK_UP:		keyScroll(SPoint(0, 1));	dirty = true;	break;
				case SDLK_DOWN:		keyScroll(SPoint(0,-1));	dirty = true;	break;
				case SDLK_LEFT:		keyScroll(SPoint( 1,0));	dirty = true;	break;
				case SDLK_RIGHT:	keyScroll(SPoint(-1,0));	dirty = true;	break;
				}
			}
			break;
		}
		if(dirty)
			setDirty(0);
		return handled || dirty;
	}
	void draw(SDL_Surface * a_surface)
	{
		if(VSDL_pushClip(a_surface, *getArea()))
		{
			for(int i = 0; i < m_children.size(); ++i)
			{
				Panel* child = m_children.get(i);
				if(child)
				{
					SRect * childArea = child->getArea();
					if(childArea->intersects(VSDL_getClip(a_surface)))
						child->draw(a_surface);
				}
			}
			VSDL_popClip(a_surface);
		}
	}
};